Tutustu Reactin useReducer-hookiin monimutkaisen tilan hallinnassa. Opas kattaa edistyneet mallit, suorituskyvyn optimoinnin ja käytännön esimerkkejä kehittäjille.
React useReducer: Monimutkaisten tilanhallintamallien hallinta
Reactin useReducer-hook on tehokas työkalu monimutkaisen tilan hallintaan sovelluksissasi. Toisin kuin useState, joka sopii usein yksinkertaisempiin tilapäivityksiin, useReducer loistaa käsiteltäessä monimutkaista tilalogiikkaa ja päivityksiä, jotka riippuvat edellisestä tilasta. Tämä kattava opas syventyy useReducerin yksityiskohtiin, tutkii edistyneitä malleja ja tarjoaa käytännön esimerkkejä kehittäjille maailmanlaajuisesti.
useReducerin perusteiden ymmärtäminen
Pohjimmiltaan useReducer on tilanhallintatyökalu, joka on saanut inspiraationsa Redux-mallista. Se hyväksyy kaksi argumenttia: reducer-funktion ja alkutilan. Reducer-funktio käsittelee tilasiirtymiä lähetettyjen (dispatched) toimintojen perusteella. Tämä malli edistää puhtaampaa koodia, helpompaa virheenkorjausta ja ennustettavia tilapäivityksiä, jotka ovat ratkaisevan tärkeitä kaikenkokoisissa sovelluksissa. Käydään läpi sen komponentit:
- Reducer-funktio: Tämä on
useReducerinydin. Se ottaa nykyisen tilan ja toiminto-objektin syötteenä ja palauttaa uuden tilan. Toiminto-objektilla on tyypillisestitype-ominaisuus, joka kuvaa suoritettavan toiminnon, ja se voi sisältääpayload-osan lisätietojen kera. - Alkutila: Tämä on sovelluksesi tilan lähtökohta.
- Dispatch-funktio: Tämä funktio mahdollistaa tilapäivitysten käynnistämisen lähettämällä toimintoja.
useReducertarjoaa dispatch-funktion.
Tässä on yksinkertainen esimerkki, joka havainnollistaa perusrakennetta:
import React, { useReducer } from 'react';
// Määritellään reducer-funktio
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
// Alustetaan useReducer
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Määrä: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Lisää</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Vähennä</button>
</div>
);
}
export default Counter;
Tässä esimerkissä reducer-funktio käsittelee lisäys- ja vähennystoimintoja päivittäen `count`-tilaa. dispatch-funktiota käytetään näiden tilasiirtymien käynnistämiseen.
Edistyneet useReducer-mallit
Vaikka useReducerin perusmalli on suoraviivainen, sen todellinen voima tulee esiin, kun aletaan käsitellä monimutkaisempaa tilalogiikkaa. Tässä on joitakin edistyneitä malleja harkittavaksi:
1. Monimutkaiset toimintojen payloadit (hyötykuormat)
Toiminnot eivät tarvitse olla yksinkertaisia merkkijonoja, kuten 'increment' tai 'decrement'. Ne voivat kuljettaa monipuolista tietoa. Payloadien avulla voit välittää dataa reducerille dynaamisempia tilapäivityksiä varten. Tämä on erittäin hyödyllistä lomakkeissa, API-kutsuissa ja listojen hallinnassa.
function reducer(state, action) {
switch (action.type) {
case 'add_item':
return { ...state, items: [...state.items, action.payload] };
case 'remove_item':
return { ...state, items: state.items.filter(item => item.id !== action.payload) };
default:
return state;
}
}
// Esimerkki toiminnon lähettämisestä
dispatch({ type: 'add_item', payload: { id: 1, name: 'Item 1' } });
dispatch({ type: 'remove_item', payload: 1 }); // Poista alkio, jonka id on 1
2. Useiden reducervien käyttö (Reducer Composition)
Suuremmissa sovelluksissa kaikkien tilasiirtymien hallinta yhdessä reducerissa voi käydä hankalaksi. Reducerien yhdistäminen (composition) mahdollistaa tilanhallinnan jakamisen pienempiin, hallittavampiin osiin. Voit saavuttaa tämän yhdistämällä useita reducereita yhdeksi ylätason reduceriksi.
// Yksittäiset reducerit
function itemReducer(state, action) {
switch (action.type) {
case 'add_item':
return { ...state, items: [...state.items, action.payload] };
case 'remove_item':
return { ...state, items: state.items.filter(item => item.id !== action.payload) };
default:
return state;
}
}
function filterReducer(state, action) {
switch(action.type) {
case 'SET_FILTER':
return {...state, filter: action.payload}
default:
return state;
}
}
// Reducerien yhdistäminen
function combinedReducer(state, action) {
return {
items: itemReducer(state.items, action),
filter: filterReducer(state.filter, action)
};
}
// Alkutila (esimerkki)
const initialState = {
items: [],
filter: 'all'
};
function App() {
const [state, dispatch] = useReducer(combinedReducer, initialState);
return (
<div>
{/* UI-komponentit, jotka käynnistävät toimintoja combinedReducerissa */}
</div>
);
}
3. `useReducerin` hyödyntäminen Context API:n kanssa
Context API tarjoaa tavan välittää dataa komponenttipuun läpi ilman, että propseja tarvitsee välittää manuaalisesti joka tasolla. Yhdistettynä useReducerin kanssa se luo tehokkaan ja suorituskykyisen tilanhallintaratkaisun, jota pidetään usein kevyenä vaihtoehtona Reduxille. Tämä malli on poikkeuksellisen hyödyllinen globaalin sovellustilan hallinnassa.
import React, { createContext, useContext, useReducer } from 'react';
// Luodaan konteksti tilallemme
const AppContext = createContext();
// Määritellään reducer ja alkutila (kuten aiemmin)
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
const initialState = { count: 0 };
// Luodaan provider-komponentti
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
// Luodaan oma hook helppoa käyttöä varten
function useAppState() {
return useContext(AppContext);
}
function Counter() {
const { state, dispatch } = useAppState();
return (
<div>
<p>Määrä: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Lisää</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Vähennä</button>
</div>
);
}
function App() {
return (
<AppProvider>
<Counter />
</AppProvider>
);
}
Tässä AppContext tarjoaa tilan ja dispatch-funktion kaikille lapsikomponenteille. Oma useAppState-hook yksinkertaistaa kontekstin käyttöä.
4. Thunkien toteuttaminen (asynkroniset toiminnot)
useReducer on oletusarvoisesti synkroninen. Monissa sovelluksissa on kuitenkin tarpeen suorittaa asynkronisia operaatioita, kuten datan hakeminen API:sta. Thunkit mahdollistavat asynkroniset toiminnot. Voit saavuttaa tämän lähettämällä (dispatching) funktion ("thunk") pelkän toiminto-objektin sijaan. Funktio vastaanottaa dispatch-funktion ja voi sitten lähettää useita toimintoja asynkronisen operaation tuloksen perusteella.
function fetchUserData(userId) {
return async (dispatch) => {
dispatch({ type: 'request_user' });
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
dispatch({ type: 'receive_user', payload: user });
} catch (error) {
dispatch({ type: 'request_user_error', payload: error });
}
};
}
function reducer(state, action) {
switch (action.type) {
case 'request_user':
return { ...state, loading: true, error: null };
case 'receive_user':
return { ...state, loading: false, user: action.payload, error: null };
case 'request_user_error':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
function UserProfile({ userId }) {
const [state, dispatch] = useReducer(reducer, { loading: false, user: null, error: null });
React.useEffect(() => {
dispatch(fetchUserData(userId));
}, [userId, dispatch]);
if (state.loading) return <p>Ladataan...</p>;
if (state.error) return <p>Virhe: {state.error.message}</p>;
if (!state.user) return null;
return (
<div>
<h2>{state.user.name}</h2>
<p>Sähköposti: {state.user.email}</p>
</div>
);
}
Tämä esimerkki lähettää toimintoja lataus-, onnistumis- ja virhetiloille asynkronisen API-kutsun aikana. Saatat tarvita middleware-ohjelmiston, kuten `redux-thunk`, monimutkaisempiin skenaarioihin; yksinkertaisemmissa käyttötapauksissa tämä malli toimii kuitenkin erittäin hyvin.
Suorituskyvyn optimointitekniikat
React-sovellusten suorituskyvyn optimointi on kriittistä, erityisesti työskenneltäessä monimutkaisen tilanhallinnan parissa. Tässä on joitakin tekniikoita, joita voit käyttää useReducerin kanssa:
1. Dispatch-funktion memoisaatio
useReducerin palauttama dispatch-funktio ei tyypillisesti muutu renderöintien välillä, mutta on silti hyvä käytäntö memoisoida se, jos välität sen lapsikomponenteille tarpeettomien uudelleenrenderöintien estämiseksi. Käytä tähän React.useCallback:
const [state, dispatch] = useReducer(reducer, initialState);
const memoizedDispatch = React.useCallback(dispatch, []); // Memoisoi dispatch-funktio
Tämä varmistaa, että dispatch-funktio muuttuu vain, kun riippuvuustaulukon riippuvuudet muuttuvat (tässä tapauksessa niitä ei ole, joten se ei muutu).
2. Optimoi reducer-logiikka
Reducer-funktio suoritetaan jokaisen tilapäivityksen yhteydessä. Varmista, että reducerisi on suorituskykyinen minimoimalla tarpeettomat laskutoimitukset ja välttämällä monimutkaisia operaatioita reducer-funktion sisällä. Harkitse seuraavia:
- Muuttumattomat tilapäivitykset: Päivitä tila aina muuttumattomasti. Käytä spread-operaattoria (
...) taiObject.assign()luodaksesi uusia tilaobjekteja sen sijaan, että muokkaisit olemassa olevia suoraan. Tämä on tärkeää muutosten havaitsemiseksi ja odottamattoman käytöksen välttämiseksi. - Vältä syväkopiointia tarpeettomasti: Tee syväkopioita tilaobjekteista vain ehdottoman välttämättömissä tapauksissa. Matalat kopiot (käyttämällä spread-operaattoria yksinkertaisille objekteille) ovat yleensä riittäviä ja vähemmän laskennallisesti raskaita.
- Laiska alustus (Lazy Initialization): Jos alkutilan laskeminen on laskennallisesti raskasta, voit käyttää funktiota tilan alustamiseen. Tämä funktio suoritetaan vain kerran, ensimmäisen renderöinnin aikana.
//Laiska alustus
const [state, dispatch] = useReducer(reducer, initialState, (initialArg) => {
//Kallis alustuslogiikka tähän
return {
...initialArg,
initializedData: 'data'
}
});
3. Memoisoi monimutkaiset laskutoimitukset `useMemo`:lla
Jos komponenttisi suorittavat laskennallisesti raskaita operaatioita tilan perusteella, käytä React.useMemo-hookia tuloksen memoisaatioon. Tämä estää laskutoimituksen uudelleensuorittamisen, elleivät riippuvuudet muutu. Tämä on kriittistä suorituskyvylle suurissa sovelluksissa tai niissä, joissa on monimutkaista logiikkaa.
import React, { useReducer, useMemo } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { items: [1, 2, 3, 4, 5] });
const total = useMemo(() => {
console.log('Lasketaan kokonaissumma...'); // Tämä tulostuu vain, kun riippuvuudet muuttuvat
return state.items.reduce((sum, item) => sum + item, 0);
}, [state.items]); // Riippuvuustaulukko: laske uudelleen, kun itemit muuttuvat
return (
<div>
<p>Yhteensä: {total}</p>
{/* ... muut komponentit ... */}
</div>
);
}
Käytännön esimerkkejä useReducerista
Katsotaanpa joitakin käytännön käyttötapauksia useReducerista, jotka havainnollistavat sen monipuolisuutta. Nämä esimerkit ovat relevantteja kehittäjille maailmanlaajuisesti, eri projektityypeissä.
1. Lomakkeen tilan hallinta
Lomakkeet ovat yleinen osa mitä tahansa sovellusta. useReducer on loistava tapa käsitellä monimutkaista lomakkeen tilaa, mukaan lukien useat syötekentät, validointi ja lähetyslogiikka. Tämä malli edistää ylläpidettävyyttä ja vähentää boilerplate-koodia.
import React, { useReducer } from 'react';
function formReducer(state, action) {
switch (action.type) {
case 'change':
return {
...state,
[action.field]: action.value,
};
case 'submit':
//Suorita lähetyslogiikka (API-kutsut jne.)
return state;
case 'reset':
return {name: '', email: '', message: ''};
default:
return state;
}
}
function ContactForm() {
const [state, dispatch] = useReducer(formReducer, { name: '', email: '', message: '' });
const handleSubmit = (event) => {
event.preventDefault();
dispatch({type: 'submit'});
// Esimerkki API-kutsusta (käsitteellinen)
// fetch('/api/contact', { method: 'POST', body: JSON.stringify(state) });
alert('Lomake lähetetty (käsitteellisesti)!')
dispatch({type: 'reset'});
};
const handleChange = (event) => {
dispatch({ type: 'change', field: event.target.name, value: event.target.value });
};
return (
<form onSubmit={handleSubmit}>
<label htmlFor="name">Nimi:</label>
<input type="text" id="name" name="name" value={state.name} onChange={handleChange} />
<label htmlFor="email">Sähköposti:</label>
<input type="email" id="email" name="email" value={state.email} onChange={handleChange} />
<label htmlFor="message">Viesti:</label>
<textarea id="message" name="message" value={state.message} onChange={handleChange} />
<button type="submit">Lähetä</button>
</form>
);
}
export default ContactForm;
Tämä esimerkki hallitsee tehokkaasti lomakkeen kenttien tilaa ja käsittelee sekä syötteiden muutoksia että lomakkeen lähettämistä. Huomaa `reset`-toiminto, joka nollaa lomakkeen onnistuneen lähetyksen jälkeen. Se on tiivis ja helppotajuinen toteutus.
2. Ostoskorin toteuttaminen
Verkkokauppasovellukset, jotka ovat suosittuja maailmanlaajuisesti, sisältävät usein ostoskorin hallinnan. useReducer sopii erinomaisesti tuotteiden lisäämisen, poistamisen ja päivittämisen monimutkaisuuden käsittelyyn ostoskorissa.
function cartReducer(state, action) {
switch (action.type) {
case 'add_item':
const existingItemIndex = state.items.findIndex(item => item.id === action.payload.id);
if (existingItemIndex !== -1) {
// Jos tuote on olemassa, lisää määrää
const updatedItems = [...state.items];
updatedItems[existingItemIndex] = { ...updatedItems[existingItemIndex], quantity: updatedItems[existingItemIndex].quantity + 1 };
return { ...state, items: updatedItems };
}
return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] };
case 'remove_item':
return { ...state, items: state.items.filter(item => item.id !== action.payload) };
case 'update_quantity':
const itemIndex = state.items.findIndex(item => item.id === action.payload.id);
if (itemIndex !== -1) {
const updatedItems = [...state.items];
updatedItems[itemIndex] = { ...updatedItems[itemIndex], quantity: action.payload.quantity };
return { ...state, items: updatedItems };
}
return state;
case 'clear_cart':
return { ...state, items: [] };
default:
return state;
}
}
function ShoppingCart() {
const [state, dispatch] = React.useReducer(cartReducer, { items: [] });
const handleAddItem = (item) => {
dispatch({ type: 'add_item', payload: item });
};
const handleRemoveItem = (itemId) => {
dispatch({ type: 'remove_item', payload: itemId });
};
const handleUpdateQuantity = (itemId, quantity) => {
dispatch({ type: 'update_quantity', payload: {id: itemId, quantity} });
}
// Laske kokonaissumma
const total = React.useMemo(() => {
return state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [state.items]);
return (
<div>
<h2>Ostoskori</h2>
{state.items.length === 0 && <p>Ostoskorisi on tyhjä.</p>}
<ul>
{state.items.map(item => (
<li key={item.id}>
{item.name} - ${item.price} x {item.quantity} = ${item.price * item.quantity}
<button onClick={() => handleRemoveItem(item.id)}>Poista</button>
<input type="number" min="1" value={item.quantity} onChange={(e) => handleUpdateQuantity(item.id, parseInt(e.target.value))} />
</li>
))}
</ul>
<p>Yhteensä: ${total}</p>
<button onClick={() => dispatch({ type: 'clear_cart' })}>Tyhjennä ostoskori</button>
{/* ... muut komponentit ... */}
</div>
);
}
Ostoskorin reducer hallitsee tuotteiden lisäämistä, poistamista ja päivittämistä niiden määrien kanssa. React.useMemo-hookia käytetään kokonaishinnan tehokkaaseen laskemiseen. Tämä on yleinen ja käytännöllinen esimerkki käyttäjän maantieteellisestä sijainnista riippumatta.
3. Yksinkertaisen kytkimen toteuttaminen pysyvällä tilalla
Tämä esimerkki näyttää, kuinka useReducer yhdistetään paikalliseen tallennustilaan (local storage) pysyvän tilan luomiseksi. Käyttäjät odottavat usein, että heidän asetuksensa muistetaan. Tämä malli käyttää selaimen paikallista tallennustilaa kytkimen tilan tallentamiseen, jopa sivun päivityksen jälkeen. Tämä toimii hyvin teemoille, käyttäjäasetuksille ja muille.
import React, { useReducer, useEffect } from 'react';
// Reducer-funktio
function toggleReducer(state, action) {
switch (action.type) {
case 'toggle':
return { isOn: !state.isOn };
default:
return state;
}
}
function ToggleWithPersistence() {
// Hae alkutila paikallisesta tallennustilasta tai oletusarvoisesti false
const [state, dispatch] = useReducer(toggleReducer, { isOn: JSON.parse(localStorage.getItem('toggleState')) || false });
// Käytä useEffectiä tilan tallentamiseen paikalliseen tallennustilaan aina sen muuttuessa
useEffect(() => {
localStorage.setItem('toggleState', JSON.stringify(state.isOn));
}, [state.isOn]);
return (
<div>
<button onClick={() => dispatch({ type: 'toggle' })}>
{state.isOn ? 'Päällä' : 'Pois päältä'}
</button>
<p>Kytkin on: {state.isOn ? 'Päällä' : 'Pois päältä'}</p>
</div>
);
}
export default ToggleWithPersistence;
Tämä yksinkertainen komponentti vaihtaa tilaa ja tallentaa sen `localStorageen`. useEffect-hook varmistaa, että tila tallennetaan jokaisen päivityksen yhteydessä. Tämä malli on tehokas työkalu käyttäjäasetusten säilyttämiseen istuntojen välillä, mikä on tärkeää maailmanlaajuisesti.
Milloin valita useReducer useState:n sijaan
Päätös useReducerin ja useState:n välillä riippuu tilasi monimutkaisuudesta ja siitä, miten se muuttuu. Tässä on opas, joka auttaa sinua tekemään oikean valinnan:
- Valitse
useReducer, kun: - Tilasi logiikka on monimutkainen ja sisältää useita ala-arvoja.
- Seuraava tila riippuu edellisestä tilasta.
- Sinun on hallittava tilapäivityksiä, jotka sisältävät lukuisia toimintoja.
- Haluat keskittää tilalogiikan ja tehdä siitä helpommin virheenkorjattavaa.
- Ennakoit tarvetta skaalata sovellustasi tai refaktoroida tilanhallintaa myöhemmin.
- Valitse
useState, kun: - Tilasi on yksinkertainen ja edustaa yhtä arvoa.
- Tilapäivitykset ovat suoraviivaisia eivätkä riipu edellisestä tilasta.
- Sinulla on suhteellisen pieni määrä tilapäivityksiä.
- Haluat nopean ja helpon ratkaisun perustason tilanhallintaan.
Yleissääntönä on, että jos huomaat kirjoittavasi monimutkaista logiikkaa useState-päivitysfunktioihisi, se on hyvä merkki siitä, että useReducer saattaa olla parempi vaihtoehto. useReducer-hook johtaa usein puhtaampaan ja ylläpidettävämpään koodiin tilanteissa, joissa on monimutkaisia tilasiirtymiä. Se voi myös auttaa tekemään koodistasi helpommin yksikkötestattavaa, koska se tarjoaa johdonmukaisen mekanismin tilapäivitysten suorittamiseen.
Parhaat käytännöt ja huomioitavat asiat
Saadaksesi kaiken irti useReducerista, pidä nämä parhaat käytännöt ja huomioitavat asiat mielessä:
- Järjestä toiminnot: Määrittele toimintotyypit vakioina (esim. `const INCREMENT = 'increment';`) välttääksesi kirjoitusvirheitä ja tehdäkseen koodistasi ylläpidettävämpää. Harkitse action creator -mallin käyttöä toimintojen luomisen kapselointiin.
- Tyyppitarkistus: Suuremmissa projekteissa harkitse TypeScriptin käyttöä tilan, toimintojen ja reducer-funktion tyypittämiseen. Tämä auttaa estämään virheitä ja parantaa koodin luettavuutta ja ylläpidettävyyttä.
- Testaus: Kirjoita yksikkötestejä reducer-funktioillesi varmistaaksesi, että ne toimivat oikein ja käsittelevät erilaisia toimintoskenaarioita. Tämä on ratkaisevan tärkeää varmistaaksesi, että tilapäivityksesi ovat ennustettavia ja luotettavia.
- Suorituskyvyn seuranta: Käytä selaimen kehittäjätyökaluja (kuten React DevTools) tai suorituskyvyn seurantatyökaluja komponenttiesi suorituskyvyn seuraamiseen ja tilapäivityksiin liittyvien pullonkaulojen tunnistamiseen.
- Tilan rakenteen suunnittelu: Suunnittele tilasi rakenne huolellisesti välttääksesi tarpeetonta sisäkkäisyyttä tai monimutkaisuutta. Hyvin jäsennelty tila tekee sen ymmärtämisestä ja hallinnasta helpompaa.
- Dokumentointi: Dokumentoi reducer-funktiosi ja toimintotyyppisi selkeästi, erityisesti yhteistyöprojekteissa. Tämä auttaa muita kehittäjiä ymmärtämään koodiasi ja helpottaa sen ylläpitoa.
- Harkitse vaihtoehtoja (Redux, Zustand jne.): Erittäin suurissa sovelluksissa, joissa on äärimmäisen monimutkaisia tilavaatimuksia, tai jos tiimisi on jo perehtynyt Reduxiin, saatat haluta harkita kattavamman tilanhallintakirjaston käyttöä. Kuitenkin
useReducerja Context API tarjoavat tehokkaan ratkaisun ilman ulkoisten kirjastojen tuomaa lisämonimutkaisuutta.
Yhteenveto
Reactin useReducer-hook on tehokas ja joustava työkalu monimutkaisen tilan hallintaan sovelluksissasi. Ymmärtämällä sen perusteet, hallitsemalla edistyneitä malleja ja ottamalla käyttöön suorituskyvyn optimointitekniikoita voit rakentaa vankempia, ylläpidettävämpiä ja tehokkaampia React-komponentteja. Muista räätälöidä lähestymistapasi projektisi tarpeiden mukaan. Monimutkaisten lomakkeiden hallinnasta ostoskorien rakentamiseen ja pysyvien asetusten käsittelyyn useReducer antaa kehittäjille maailmanlaajuisesti mahdollisuuden luoda hienostuneita ja käyttäjäystävällisiä käyttöliittymiä. Kun syvennät React-kehityksen maailmaan, useReducerin hallitseminen osoittautuu korvaamattomaksi voimavaraksi työkalupakissasi. Muista aina priorisoida koodin selkeyttä ja ylläpidettävyyttä varmistaaksesi, että sovelluksesi pysyvät helposti ymmärrettävinä ja kehitettävinä ajan myötä.